/*  tBookShelf: TextPlugin
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <sys/time.h>
#include <time.h>

#include <unistd.h>

#include <sstream>

#include "Utils/DebugStream.hpp"



template <class CharT, class Traits>
class LogStreamBuff : public std::basic_streambuf<CharT, Traits>
{
public:
    //Types
    typedef CharT  char_type;
    typedef Traits traits_type;
 
    typedef std::basic_streambuf<char_type, traits_type> streambuf_type;
    typedef typename streambuf_type::int_type            int_type;

private:
    // Disable copy operations with the buffer
    LogStreamBuff(const LogStreamBuff&);
    LogStreamBuff& operator=(const LogStreamBuff&);

public:
    explicit LogStreamBuff(const std::string& name, 
                           int    flags = O_APPEND| O_CREAT | O_WRONLY, 
                           mode_t mode  = S_IRUSR | S_IWUSR)
        : streambuf_type(),
          fd(-1)
    {    
        fd = ::open(name.c_str(), flags, mode);

        if (-1 == fd)
        {
            throw std::runtime_error(std::string("Open filelog ")+name);
        }
        
        static const int DEF_LEN = 256;

        char_type* l_buff = new char_type[DEF_LEN + 1];

        setp(l_buff, l_buff + DEF_LEN);
    }

    ~LogStreamBuff() throw()
    {
        ::close(fd);

        char_type* l_buff = this->pbase();

        delete[] l_buff;
    }

    /// Setups the message's level
    void setMessPriority(const LogLevel pr) throw()
    {
        curr_prio = pr;
    }

    LogLevel messPriority() const throw()
    {
        return curr_prio;
    }

    /// Setups the log's level, so all messages with priority less than
    /// pr will be discarded
    void setLevel(const LogLevel pr) throw()
    {
        level = pr;
    }

protected:
    bool couldPrint() const throw()
    {
        return curr_prio <= level;
    }

    virtual std::streamsize xsputn(const char_type* s, 
                                   std::streamsize  n);
    virtual int_type overflow(int_type ch);

    virtual int sync(void);
private:
    void reallocate_buffer(std::streamsize increase);

private:
    LogLevel   level;
    LogLevel   curr_prio;

    int    fd;
};


/// Manipulator for loglevel priority definition
struct _priority { LogLevel _prio; };

/// Actualy, it is manipulator
inline _priority priority(const LogLevel prio) throw()
{
    _priority x_p;
    x_p._prio = prio;
    return x_p;
}

/// Method to do all nesessary work
template <class CharT, class Traits>
std::basic_ostream<CharT, Traits>&
operator <<(std::basic_ostream<CharT, Traits>& os,
            const _priority& which)
{
    LogStreamBuff<CharT, Traits>* buff = 
        dynamic_cast<LogStreamBuff<CharT, Traits> * >(os.rdbuf());

    if (buff)
        buff->setMessPriority(which._prio);

    return os;
}

/// Timestamp orinting
struct _TimeStamp{ std::string stamp_data;};

inline _TimeStamp time_stamp(const std::string& format)
{
    _TimeStamp stamp;
    stamp.stamp_data = format;

    return stamp;
}
inline _TimeStamp time_stamp(const char* format)
{
    _TimeStamp stamp;
    stamp.stamp_data.assign(format);

    return stamp;
}


template <class CharT, class Traits>
std::basic_ostream<CharT, Traits>&
operator <<(std::basic_ostream<CharT, Traits>& os,
            const _TimeStamp& which)
{
    std::stringstream buf;

    struct timeval tv;
    gettimeofday(&tv, NULL);
    time_t tt = tv.tv_sec;

    tm timt;
    localtime_r(&tt,&timt); 

    for (size_t i = 0; i < which.stamp_data.length(); ++i)
    {
	switch (which.stamp_data[i])
	{
	    case 'Y': buf << (timt.tm_year+1900); break ;
	    case 'M': buf << (timt.tm_mon+1);     break ;
	    case 'D': buf << timt.tm_mday;        break ;
	    case 'h': buf << timt.tm_hour;        break ;
	    case 'm': buf << timt.tm_min;         break ;
	    case 's': buf << timt.tm_sec;         break ;
	    case 'u': buf << tv.tv_usec/1000;     break ;
	    default: if (which.stamp_data[i] >= 0x20)
                buf  << which.stamp_data[i];
	}
    }
    os << buf.str();

    return os;
}

template <class CharT, class Traits>
typename LogStreamBuff<CharT, Traits>::int_type
LogStreamBuff<CharT, Traits>::overflow(LogStreamBuff<CharT, Traits>::int_type ch)
{
    if (!couldPrint())
        return traits_type::not_eof(ch);

    if (!traits_type::eq_int_type(ch, traits_type::eof()))
    {
        static const int APP_LEN = 64;
        
        this->reallocate_buffer(APP_LEN);
        sputc(ch);
    }

    return traits_type::not_eof(ch);
}

template <class CharT, class Traits>
void
LogStreamBuff<CharT, Traits>::reallocate_buffer(std::streamsize increase)
{
    size_t old_sz = this->epptr() - this->pbase();
    size_t curr_p = this->pptr() - this->pbase();

    size_t new_sz = old_sz + increase + 1;

    char_type* l_buff = this->pbase();
    char_type* buff  = new char_type[new_sz];

    traits_type::copy(buff, l_buff, old_sz);

    this->setp(buff, buff + new_sz - 1);

    this->pbump(curr_p);

    delete[] l_buff;
}

template <class CharT, class Traits>
int LogStreamBuff<CharT, Traits>::sync(void)
{
    int pp = this->pptr() - this->pbase();
        
    if (this->couldPrint())
    {
        ::write(this->fd, this->pbase(), pp);
    }
    this->pbump(-pp);
        
    return 0;
}


template <class CharT, class Traits>
std::streamsize
LogStreamBuff<CharT, Traits>::xsputn(const char_type *s, 
                                     std::streamsize n)
{
    if (!couldPrint())
        return n;
        
    std::streamsize avail = this->epptr() - this->pptr();

    if (n > avail)
    {
        this->reallocate_buffer(n - avail + 1); // Allow nex symbol be outputted too
    }
     
    traits_type::copy(this->pptr(), s, n);
    this->pbump(n);
    
    return n;
}

typedef LogStreamBuff<char, std::char_traits<char> > LogBuff;

std::ostream dout(0);

void initDebug(ConfigParser* cfg)
{
    LogLevel loglevel = LogDebug;

    std::string fname = cfg->getStringVariable("DebugFile");
    if (fname.empty())
    {
        fname = "/dev/null";
        loglevel = LogNone;
    }

    std::auto_ptr<LogBuff> buff(new LogBuff(fname));
    buff->setMessPriority(LogDebug);
    buff->setLevel(LogDebug);

    std::streambuf* old = dout.rdbuf();
    if (old)
        delete old;

    dout.rdbuf(buff.get());

    buff.release();
}
